package com.itcoon.cloud.framework.redis.lock;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * Redisson分布式锁实现-使用可重入非公平锁（RedissonLock）
 */
@Component
public class RedissonDistributedLocker implements DistributedLocker {

    private static final Logger log = LoggerFactory.getLogger(RedissonDistributedLocker.class);

    private final RedissonClient redissonClient;
    private final PlatformTransactionManager transactionManager;

    public RedissonDistributedLocker(RedissonClient redissonClient, PlatformTransactionManager transactionManager) {
        this.redissonClient = redissonClient;
        this.transactionManager = transactionManager;
    }

    public RLock lock(String lockKey) {
        RLock lock = this.redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }

	public RLock lock(String lockKey, long timeout) {
        RLock lock = this.redissonClient.getLock(lockKey);
        lock.lock(timeout, TimeUnit.SECONDS);
        return lock;
    }

	public RLock lock(String lockKey, TimeUnit unit, long timeout) {
        RLock lock = this.redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

	public boolean tryLock(String lockKey, TimeUnit unit, long leaseTime) {
        RLock lock = this.redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(0L, leaseTime, unit);
        } catch (InterruptedException var7) {
            return false;
        }
    }

	public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime){
		RLock lock = redissonClient.getLock(lockKey);
		try {
	    	return lock.tryLock(waitTime, leaseTime, unit);
	    } catch (InterruptedException e) {
			return false;
		}
	}

    @Override
    public <T> T tryLockAndRun(String lockKey, TimeUnit unit, long waitTime, long leaseTime, Supplier<T> supplier, String scene, boolean isTransaction) {
        final LocalDateTime start = LocalDateTime.now();
        // 获取分布式锁，最长等待时间:10秒,20秒后自动释放。注意锁与事务的顺序：获取分布式锁 -> 开启事务 -> 执行业务 -> 提交事务 -> 释放分布式锁！！！
        final boolean tryLock = this.tryLock(lockKey, unit, waitTime, leaseTime);
        final LocalDateTime end = LocalDateTime.now();
        if (!tryLock) {
            log.error("[{}]获取分布式锁失败，lockKey = {}，耗时{}ms", scene, lockKey, Duration.between(start, end).toMillis());
            throw new RuntimeException("获取分布式锁失败");
        }
        try {
            log.info("[{}]获取分布式锁成功，lockKey = {}，耗时{}ms", scene, lockKey, Duration.between(start, end).toMillis());
            TransactionStatus transactionStatus = null;
            if(isTransaction){
                DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
                defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
                transactionStatus = transactionManager.getTransaction(defaultTransactionDefinition);
            }
            T t = null;
            try{
                t = supplier.get();
                if(transactionStatus != null){
                    transactionManager.commit(transactionStatus);
                }
                return t;
            }catch (RuntimeException e){
                if(transactionStatus != null){
                    transactionManager.rollback(transactionStatus);
                }
                throw e;
            }
        } finally {
            this.unlock(lockKey);
        }
    }

    public void unlock(String lockKey){
	    RLock lock = redissonClient.getLock(lockKey);
	    try {
	        if (lock.isLocked()) {
	            lock.unlock();
	        }
	    } catch (IllegalMonitorStateException localIllegalMonitorStateException) {}
	}

	public void unlock(RLock lock) {
        try {
            if (lock.isLocked()) {
                lock.unlock();
            }
        } catch (IllegalMonitorStateException var3) {}
    }
}

